ETF.jpg

First code project by Anton Kruse¶

Welcome to my first own project including python code. This project seeks to give an overview of the largest ETFs provided by the most common ETF suppliers. Personally, I invest small amounts into ETF since the beginning of 2018. Now, I would like to examine, if my choices have been evolved as good as the picked ETF. Furthermore, the scope tries to shed some light into optimal weighting approaches, such that, this notebook tries to perform several optimization methods. Finally, I would like to simulate different portfolio values, if I would have invested monthly a certain amount.

This notebook is only personal motivated and will certainly lack at many point. Diversification will not be covered, since picking the largest ETFs according to their Net Assets does not include any deaper strategy.

In [1]:
# import needed packages
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from PIL import Image
import pandas_datareader.data as web
import datetime
import yfinance as yf
from matplotlib import dates
from pypfopt.expected_returns import mean_historical_return
from pypfopt.risk_models import CovarianceShrinkage
from pypfopt.efficient_frontier import EfficientFrontier
from pypfopt import plotting
import dash
from dash import html, dcc
import jupyterthemes as jt
from jupyterthemes import get_themes
from jupyterthemes.stylefx import set_nb_theme
In [2]:
set_nb_theme('oceans16')
Out[2]:

Iternary for this project?¶

Step 1: Load the data.

Step 2: Give an overview about the data and the performance.

Step 3: Perform portfolio optimization.

Step 4: Create an interactive dashboard for clean presentation.

Explaination of the ETFs for deaper understanding & better comparison¶

IVV: iShares Core S&P 500 ETF

IEFA: iShares Core MSCI EAFE ETF (Europe, Australasia, Far East)

AGG: iShares Core U.S. Aggregate Bond ETF (fixed income securities)

IJR: iShares Core S&P Small-Cap ETF

SPX.MI: Lyxor S&P 500 UCITS ETF

WLDC.MI: Lyxor MSCI World UCITS ETF Acc

USRI.PA: Amundi Index Solutions - Amundi MSCI USA SRI UCITS ETF (track the performance of MSCI USA SRI Filtered ex Fossil Fuels Index)

MEUD.PA: Lyxor Core STOXX Europe 600

URTH: iShares MSCI World ETF

AEEM.PA: Amundi Index Solutions - Amundi MSCI Emerging Markets

IUHE.AS: iShares V PLC - iShares S&P 500 Health Care Sector UCITS ETF

ICOM.L: iShares Diversified Commodity Swap UCITS ETF

In [3]:
# Step 1 - importing data by defining a for loop that gets the tickers from a list
start = datetime.datetime(2018, 1, 1)
end = datetime.datetime.today()

ticker_ishares = ['IVV', 'IEFA', 'AGG', 'IJR']
ticker_amundi = ['SPX.MI', 'WLDC.MI', 'USRI.PA', 'MEUD.PA'] 
antons_ticker = ['URTH', 'AEEM.PA', 'IUHE.AS', 'ICOM.L']
data_ishares = pd.DataFrame(columns=ticker_ishares)
data_amundi = pd.DataFrame(columns=ticker_amundi)
antons_data = pd.DataFrame(columns=antons_ticker)

for i in ticker_ishares:
    data_ishares[i] = yf.download(i, start, end)['Adj Close']
for i in ticker_amundi:
    data_amundi[i] = yf.download(i, start, end)['Adj Close']
for i in antons_ticker:
    antons_data[i] = yf.download(i, start, end)['Adj Close']
[*********************100%***********************]  1 of 1 completed
[*********************100%***********************]  1 of 1 completed
[*********************100%***********************]  1 of 1 completed
[*********************100%***********************]  1 of 1 completed
[*********************100%***********************]  1 of 1 completed
[*********************100%***********************]  1 of 1 completed
[*********************100%***********************]  1 of 1 completed
[*********************100%***********************]  1 of 1 completed
[*********************100%***********************]  1 of 1 completed
[*********************100%***********************]  1 of 1 completed
[*********************100%***********************]  1 of 1 completed
[*********************100%***********************]  1 of 1 completed

Iternary for Step 2:¶

  1. Descriptive statistic
  2. Plot absolute & relative change over time
  3. Show key facts for every ETF
In [4]:
# Step 2- descriptive statistic of the data set
data = [data_ishares, data_amundi, antons_data]

for i in data:
    print('This shows the descriptive statistics for {}'.format(i.columns))
    describe = i.describe().transpose()
    display(np.round(describe, 2))
This shows the descriptive statistics for Index(['IVV', 'IEFA', 'AGG', 'IJR'], dtype='object')
count mean std min 25% 50% 75% max
IVV 1221.0 332.42 72.28 214.95 266.94 311.80 403.07 474.32
IEFA 1221.0 60.20 7.41 40.19 55.33 57.91 66.66 75.18
AGG 1221.0 105.04 6.77 93.42 98.31 105.92 111.81 114.53
IJR 1221.0 85.24 16.43 47.15 72.94 79.72 102.93 118.39
This shows the descriptive statistics for Index(['SPX.MI', 'WLDC.MI', 'USRI.PA', 'MEUD.PA'], dtype='object')
count mean std min 25% 50% 75% max
SPX.MI 1232.0 31.03 6.49 21.28 25.41 29.11 37.95 43.40
WLDC.MI 349.0 10.95 0.43 9.88 10.64 10.86 11.30 11.84
USRI.PA 1055.0 69.17 15.45 44.04 55.85 65.48 84.30 98.10
MEUD.PA 1232.0 166.58 21.41 114.63 150.76 159.00 185.63 212.45
This shows the descriptive statistics for Index(['URTH', 'AEEM.PA', 'IUHE.AS', 'ICOM.L'], dtype='object')
count mean std min 25% 50% 75% max
URTH 1221.0 99.36 18.17 64.65 83.33 94.18 115.53 134.88
AEEM.PA 1211.0 4.41 0.47 3.16 4.07 4.27 4.80 5.48
IUHE.AS 603.0 6.22 0.52 5.00 5.80 6.34 6.61 7.20
ICOM.L 1197.0 5.50 1.08 3.72 4.86 5.15 5.97 8.65
In [5]:
# plotting the absolute change over time for the data
# set style
sns.set_style('darkgrid')
sns.set_palette('colorblind')

fig, ax = plt.subplots(figsize=(10,10), dpi=200, nrows= 3)

ax[0].plot(data_ishares['IVV'], label="iShares Core S&P 500", lw=2)
ax[0].plot(data_ishares['IEFA'], label=" iShares Core MSCI EAFE", lw=2)
ax[0].plot(data_ishares['AGG'], label="iShares Core U.S. Aggregate Bond",lw=2)
ax[0].plot(data_ishares['IJR'], label="iShares Core S&P Small-Cap",lw=2)

ax[1].plot(data_amundi['SPX.MI'], label="Lyxor S&P 500 UCITS", lw=2)
ax[1].plot(data_amundi['WLDC.MI'], label="Lyxor MSCI World UCITS", lw=2)
ax[1].plot(data_amundi['USRI.PA'], label="Amundi Index Solutions",lw=2)
ax[1].plot(data_amundi['MEUD.PA'], label="Lyxor Core STOXX Europe 600",lw=2)

ax[2].plot(antons_data['URTH'], label=" iShares MSCI World", lw=2)
ax[2].plot(antons_data['AEEM.PA'], label="Amundi MSCI Emerging Markets", lw=2)
ax[2].plot(antons_data['IUHE.AS'], label="iShares S&P 500 Health Care",lw=2)
ax[2].plot(antons_data['ICOM.L'], label="iShares Diversified Commodity Swap",lw=2)

ax[0].set_xlabel('Time')
ax[1].set_xlabel('Time')
ax[2].set_xlabel('Time')
ax[0].set_ylabel('Adjusted Close')
ax[1].set_ylabel('Adjusted Close')
ax[2].set_ylabel('Adjusted Close')


ax[0].set_title('Absolute change of the 4 largest ETFs provided by iShares according to their Net Assets in Mio.')
ax[1].set_title('Absolute change of the 4 largest ETFs provided by Amundi according to their Net Assets in Mio.')
ax[2].set_title('Absolute change of the my own 4 ETFs')

fig.suptitle('Absolute change for all 3 categories of ETFs seperatly plotted', fontsize=20)

ax[0].xaxis.set_major_locator(dates.YearLocator())
ax[0].xaxis.set_major_formatter(dates.DateFormatter("%Y"))
ax[0].tick_params(axis="x", which="major",rotation=0, pad=5)

ax[1].xaxis.set_major_locator(dates.YearLocator())
ax[1].xaxis.set_major_formatter(dates.DateFormatter("%Y"))
ax[1].tick_params(axis="x", which="major",rotation=0, pad=5)

ax[2].xaxis.set_major_locator(dates.YearLocator())
ax[2].xaxis.set_major_formatter(dates.DateFormatter("%Y"))
ax[2].tick_params(axis="x", which="major",rotation=0, pad=5)

ax[0].legend(loc ='upper left')
ax[1].legend(loc = 'upper left')
ax[2].legend(loc = 'upper left')

plt.tight_layout()
plt.show();
In [6]:
# plotting the relative change over time for the data
# set style
sns.set_style('darkgrid')
sns.set_palette('colorblind')

fig, ax = plt.subplots(figsize=(10,10), dpi=200, nrows= 3)

ax[0].plot(data_ishares['IVV'].pct_change().cumsum(), label="iShares Core S&P 500", lw=2)
ax[0].plot(data_ishares['IEFA'].pct_change().cumsum(), label=" iShares Core MSCI EAFE", lw=2)
ax[0].plot(data_ishares['AGG'].pct_change().cumsum(), label="iShares Core U.S. Aggregate Bond",lw=2)
ax[0].plot(data_ishares['IJR'].pct_change().cumsum(), label="iShares Core S&P Small-Cap",lw=2)

ax[1].plot(data_amundi['SPX.MI'].pct_change().cumsum(), label="Lyxor S&P 500 UCITS", lw=2)
ax[1].plot(data_amundi['WLDC.MI'].pct_change().cumsum(), label="Lyxor MSCI World UCITS", lw=2)
ax[1].plot(data_amundi['USRI.PA'].pct_change().cumsum(), label="Amundi Index Solutions",lw=2)
ax[1].plot(data_amundi['MEUD.PA'].pct_change().cumsum(), label="Lyxor Core STOXX Europe 600",lw=2)

ax[2].plot(antons_data['URTH'].pct_change().cumsum(), label=" iShares MSCI World", lw=2)
ax[2].plot(antons_data['AEEM.PA'].pct_change().cumsum(), label="Amundi MSCI Emerging Markets", lw=2)
ax[2].plot(antons_data['IUHE.AS'].pct_change().cumsum(), label="iShares S&P 500 Health Care",lw=2)
ax[2].plot(antons_data['ICOM.L'].pct_change().cumsum(), label="iShares Diversified Commodity Swap",lw=2)

ax[0].set_xlabel('Time')
ax[1].set_xlabel('Time')
ax[2].set_xlabel('Time')
ax[0].set_ylabel('Adjusted Close')
ax[1].set_ylabel('Adjusted Close')
ax[2].set_ylabel('Adjusted Close')


ax[0].set_title('Relative change of the 4 largest ETFs provided by iShares according to their Net Assets in Mio.')
ax[1].set_title('Relative change of the 4 largest ETFs provided by Amundi according to their Net Assets in Mio.')
ax[2].set_title('Relative change of the my own 4 ETFs')

fig.suptitle('Relative change for all 3 categories of ETFs seperatly plotted', fontsize=20)

ax[0].xaxis.set_major_locator(dates.YearLocator())
ax[0].xaxis.set_major_formatter(dates.DateFormatter("%Y"))
ax[0].tick_params(axis="x", which="major",rotation=0, pad=5)

ax[1].xaxis.set_major_locator(dates.YearLocator())
ax[1].xaxis.set_major_formatter(dates.DateFormatter("%Y"))
ax[1].tick_params(axis="x", which="major",rotation=0, pad=5)

ax[2].xaxis.set_major_locator(dates.YearLocator())
ax[2].xaxis.set_major_formatter(dates.DateFormatter("%Y"))
ax[2].tick_params(axis="x", which="major",rotation=0, pad=5)

ax[0].set_ylim((-0.5,1))
ax[1].set_ylim((-0.5,1))
ax[2].set_ylim((-0.5,1))

ax[0].legend(loc ='upper left')
ax[1].legend(loc = 'upper left')
ax[2].legend(loc = 'upper left')

plt.tight_layout()
plt.show();
In [7]:
# put everything into one graph
# plotting the relative change over time for the data
# set style
sns.set_style('darkgrid')
sns.set_palette('colorblind')

fig, ax = plt.subplots(figsize=(10,10), dpi=200)

ax.plot(data_ishares['IVV'].pct_change().cumsum(), label="iShares Core S&P 500", lw=2)
ax.plot(data_ishares['IEFA'].pct_change().cumsum(), label=" iShares Core MSCI EAFE", lw=2)
ax.plot(data_ishares['AGG'].pct_change().cumsum(), label="iShares Core U.S. Aggregate Bond",lw=2)
ax.plot(data_ishares['IJR'].pct_change().cumsum(), label="iShares Core S&P Small-Cap",lw=2)

ax.plot(data_amundi['SPX.MI'].pct_change().cumsum(), label="Lyxor S&P 500 UCITS", lw=2)
ax.plot(data_amundi['WLDC.MI'].pct_change().cumsum(), label="Lyxor MSCI World UCITS", lw=2)
ax.plot(data_amundi['USRI.PA'].pct_change().cumsum(), label="Amundi Index Solutions",lw=2)
ax.plot(data_amundi['MEUD.PA'].pct_change().cumsum(), label="Lyxor Core STOXX Europe 600",lw=2)

ax.plot(antons_data['URTH'].pct_change().cumsum(), label=" iShares MSCI World", lw=2)
ax.plot(antons_data['AEEM.PA'].pct_change().cumsum(), label="Amundi MSCI Emerging Markets", lw=2)
ax.plot(antons_data['IUHE.AS'].pct_change().cumsum(), label="iShares S&P 500 Health Care",lw=2)
ax.plot(antons_data['ICOM.L'].pct_change().cumsum(), label="iShares Diversified Commodity Swap",lw=2)

ax.set_xlabel('Time')
ax.set_ylabel('Adjusted Close')

ax.set_title('Relative change of all ETFs provided by iShares, Amundi & my own.')

fig.suptitle('Relative change for all 3 categories of ETFs', fontsize=20)

ax.xaxis.set_major_locator(dates.YearLocator())
ax.xaxis.set_major_formatter(dates.DateFormatter("%Y"))
ax.tick_params(axis="x", which="major",rotation=0, pad=5)

ax.set_ylim((-0.5,1))

plt.legend(loc = 'upper left')
plt.tight_layout()
plt.show();
In [8]:
# get a visible idea how these ETFs are correlated by focus on the development during the pandemic
sns.set_style('darkgrid')
sns.set_palette('colorblind')

fig, ax = plt.subplots(figsize=(10,10), dpi=200)

ax.plot(data_ishares['IVV']["2020-01-01":"2021-01-01"].pct_change().cumsum(), label="iShares Core S&P 500", lw=2)
ax.plot(data_ishares['IEFA']["2020-01-01":"2021-01-01"].pct_change().cumsum(), label="iShares Core MSCI EAFE", lw=2)
ax.plot(data_ishares['AGG']["2020-01-01":"2021-01-01"].pct_change().cumsum(), label="iShares Core U.S. Aggregate Bond",lw=2)
ax.plot(data_ishares['IJR']["2020-01-01":"2021-01-01"].pct_change().cumsum(), label="iShares Core S&P Small-Cap",lw=2)

ax.plot(data_amundi['SPX.MI']["2020-01-01":"2021-01-01"].pct_change().cumsum(), label="Lyxor S&P 500 UCITS", lw=2)
ax.plot(data_amundi['WLDC.MI']["2020-01-01":"2021-01-01"].pct_change().cumsum(), label="Lyxor MSCI World UCITS", lw=2)
ax.plot(data_amundi['USRI.PA']["2020-01-01":"2021-01-01"].pct_change().cumsum(), label="Amundi Index Solutions",lw=2)
ax.plot(data_amundi['MEUD.PA']["2020-01-01":"2021-01-01"].pct_change().cumsum(), label="Lyxor Core STOXX Europe 600",lw=2)

ax.plot(antons_data['URTH']["2020-01-01":"2021-01-01"].pct_change().cumsum(), label=" iShares MSCI World", lw=2)
ax.plot(antons_data['AEEM.PA']["2020-01-01":"2021-01-01"].pct_change().cumsum(), label="Amundi MSCI Emerging Markets", lw=2)
ax.plot(antons_data['IUHE.AS']["2020-01-01":"2021-01-01"].pct_change().cumsum(), label="iShares S&P 500 Health Care",lw=2)
ax.plot(antons_data['ICOM.L']["2020-01-01":"2021-01-01"].pct_change().cumsum(), label="iShares Diversified Commodity Swap",lw=2)

ax.set_xlabel('Time')
ax.set_ylabel('Adjusted Close')

ax.set_title('Relative change of all ETFs provided by iShares, Amundi & my own.')

fig.suptitle('Relative change for all 3 categories of ETFs during the pandemic', fontsize=20)

ax.xaxis.set_major_locator(dates.YearLocator())
ax.xaxis.set_major_formatter(dates.DateFormatter("%Y"))
ax.tick_params(axis="x", which="major",rotation=0, pad=5)

ax.set_ylim((-0.6,0.4))

plt.legend()
plt.tight_layout()
plt.show();

The zoomed graphs shows quite clearly, that the ETFs behave similarly. To get a furter insight into the behavior, we will have a look on their correlation.

In [9]:
# FINISH WHEN AMUNDI IS INCLUDED
all_data1 = data_ishares.merge(data_amundi, on="Date")
all_data = all_data1.merge(antons_data, on='Date')
all_data.head()
Out[9]:
IVV IEFA AGG IJR SPX.MI WLDC.MI USRI.PA MEUD.PA URTH AEEM.PA IUHE.AS ICOM.L
Date
2018-01-02 248.559158 57.914593 97.355820 72.191895 22.719999 NaN NaN 149.320007 81.489555 4.2190 NaN 5.34
2018-01-03 250.000031 58.297379 97.364738 72.126610 22.920000 NaN NaN 149.940002 81.976051 4.2489 NaN 5.33
2018-01-04 251.082840 58.871567 97.302307 72.322502 22.965000 NaN NaN 151.339996 82.343201 4.2604 NaN 5.35
2018-01-05 252.670502 59.176064 97.239906 72.630341 23.129999 NaN NaN 152.820007 82.994896 4.3045 NaN 5.30
2018-01-08 253.230316 59.132565 97.213142 72.742264 23.309999 NaN NaN 153.089996 82.792976 4.3472 NaN 5.29
In [10]:
# Show the correlation
corr = all_data.corr()
corr.style.background_gradient(cmap='coolwarm')
Out[10]:
  IVV IEFA AGG IJR SPX.MI WLDC.MI USRI.PA MEUD.PA URTH AEEM.PA IUHE.AS ICOM.L
IVV 1.000000 0.851226 0.554488 0.905307 0.967946 0.734649 0.964647 0.949259 0.991578 0.868632 0.944348 0.670173
IEFA 0.851226 1.000000 0.500347 0.914207 0.725692 0.424060 0.754676 0.888371 0.911360 0.944354 0.635533 0.486346
AGG 0.554488 0.500347 1.000000 0.306845 0.431514 0.322269 0.266694 0.408877 0.566613 0.534058 -0.309803 -0.180647
IJR 0.905307 0.914207 0.306845 1.000000 0.854104 0.617680 0.862288 0.935732 0.930844 0.896865 0.840981 0.712924
SPX.MI 0.967946 0.725692 0.431514 0.854104 1.000000 0.942311 0.997038 0.932708 0.933917 0.768360 0.935952 0.760221
WLDC.MI 0.734649 0.424060 0.322269 0.617680 0.942311 1.000000 0.922022 0.688353 0.609230 0.428212 0.679206 -0.073265
USRI.PA 0.964647 0.754676 0.266694 0.862288 0.997038 0.922022 1.000000 0.923582 0.927526 0.775955 0.931995 0.809532
MEUD.PA 0.949259 0.888371 0.408877 0.935732 0.932708 0.688353 0.923582 1.000000 0.957954 0.873596 0.949918 0.724778
URTH 0.991578 0.911360 0.566613 0.930844 0.933917 0.609230 0.927526 0.957954 1.000000 0.912628 0.876815 0.635204
AEEM.PA 0.868632 0.944354 0.534058 0.896865 0.768360 0.428212 0.775955 0.873596 0.912628 1.000000 0.530977 0.499924
IUHE.AS 0.944348 0.635533 -0.309803 0.840981 0.935952 0.679206 0.931995 0.949918 0.876815 0.530977 1.000000 0.756564
ICOM.L 0.670173 0.486346 -0.180647 0.712924 0.760221 -0.073265 0.809532 0.724778 0.635204 0.499924 0.756564 1.000000
In [11]:
# since thei graph is getting messy by adding more ETFs, a numeric comparison as reasonable
# therefore, the relativ change is shown as a number

ivv_pct = np.round((data_ishares['IVV'].pct_change().cumsum()[-1]) *100, 2)
iefa_pct = np.round((data_ishares['IEFA'].pct_change().cumsum()[-1]) *100, 2)
agg_pct = np.round((data_ishares['AGG'].pct_change().cumsum()[-1]) *100, 2)
ijr_pct = np.round((data_ishares['IJR'].pct_change().cumsum()[-1]) *100, 2)
spx_pct = np.round((data_amundi['SPX.MI'].pct_change().cumsum()[-1]) *100, 2)
wldc_pct = np.round((data_amundi['WLDC.MI'].pct_change().cumsum()[-1]) *100, 2)
usri_pct = np.round((data_amundi['USRI.PA'].pct_change().cumsum()[-1]) *100, 2)
meud_pct = np.round((data_amundi['MEUD.PA'].pct_change().cumsum()[-1]) *100, 2)
world_pct = np.round((antons_data['URTH'].pct_change().cumsum()[-1]) *100, 2)
em_pct = np.round((antons_data['AEEM.PA'].pct_change().cumsum()[-1]) *100, 2)
sp_hc_pct = np.round((antons_data['IUHE.AS'].pct_change().cumsum()[-1]) *100, 2)
com_pct = np.round((antons_data['ICOM.L'].pct_change().cumsum()[-1]) *100, 2)

pct_list = [ivv_pct, iefa_pct, agg_pct, ijr_pct, world_pct, em_pct, sp_hc_pct, com_pct, spx_pct, wldc_pct, usri_pct, meud_pct]
pct_own =[world_pct, em_pct, sp_hc_pct, com_pct]

pct_data = pd.DataFrame(pct_list, index=["iShares Core S&P 500 ETF", "iShares Core MSCI EAFE", "iShares Core U.S. Aggregate Bond"
                                         , "iShares Core S&P Small-Cap", " iShares MSCI World", "Amundi MSCI Emerging Markets", 
                                         "iShares S&P 500 Health Care", "iShares Diversified Commodity Swap",
                                         "Lyxor S&P 500 UCITS", "Lyxor MSCI World UCITS", "Amundi Index Solutions", "Lyxor Core STOXX Europe 600"], columns=["Relative change since 1-1-2018"])
pct_final = pct_data.sort_values('Relative change since 1-1-2018', ascending=False)
pct_data_own = pd.DataFrame(pct_own, index=[" iShares MSCI World", "Amundi MSCI Emerging Markets", 
                                         "iShares S&P 500 Health Care", "iShares Diversified Commodity Swap"], columns=["Relative change since 1-1-2018"])
pct_final_own = pct_data_own.sort_values('Relative change since 1-1-2018', ascending=False)
cm = sns.light_palette("green", as_cmap=True)
display(pct_final.style.background_gradient(cmap=cm))
cs = sns.light_palette("blue", as_cmap=True)
display(pct_final_own.style.background_gradient(cmap=cs))
  Relative change since 1-1-2018
Amundi Index Solutions 63.060000
Lyxor S&P 500 UCITS 62.510000
iShares Core S&P 500 ETF 53.830000
iShares Core S&P Small-Cap 46.060000
iShares Diversified Commodity Swap 37.970000
iShares MSCI World 37.110000
Lyxor Core STOXX Europe 600 28.780000
iShares S&P 500 Health Care 28.430000
Amundi MSCI Emerging Markets 12.820000
iShares Core MSCI EAFE 8.020000
Lyxor MSCI World UCITS 4.090000
iShares Core U.S. Aggregate Bond -2.160000
  Relative change since 1-1-2018
iShares Diversified Commodity Swap 37.970000
iShares MSCI World 37.110000
iShares S&P 500 Health Care 28.430000
Amundi MSCI Emerging Markets 12.820000
In [11]:
# Step 2 - show the key fact for every ETF
# to finalize: use pd.concat instead & get rid off the column names
# # can we exclude index & column names?

for i in ticker_ishares:
    print(i)
    key_facts = yf.Ticker(i).institutional_holders
    key_facts = pd.concat([key_facts, yf.Ticker(i).major_holders], axis = 0)
    key_facts = key_facts.set_index([0])
    display(key_facts)

for i in ticker_amundi:
    print(i)
    key_facts = yf.Ticker(i).institutional_holders
    key_facts = pd.concat([key_facts, yf.Ticker(i).major_holders], axis = 1)
    key_facts = key_facts.set_index([0])
    display(key_facts)
    
for i in antons_ticker:
    print(i)
    key_facts = yf.Ticker(i).institutional_holders
    key_facts = pd.concat([key_facts, yf.Ticker(i).major_holders], axis = 0)
    key_facts = key_facts.set_index([0])
    display(key_facts)
IVV
1
0
Net Assets 268.45B
NAV 386.19
PE Ratio (TTM) 2.75
Yield 1.72%
YTD Daily Total Return -17.72%
Beta (5Y Monthly) 1.00
Expense Ratio (net) 0.03%
Inception Date 2000-05-15
Previous Close 386.25
Open 385.58
Bid 383.89 x 800
Ask 383.90 x 800
Day's Range 382.98 - 385.60
52 Week Range 349.53 - 482.07
Volume 1498483
Avg. Volume 4875575
IEFA
1
0
Net Assets 75.57B
NAV 56.21
PE Ratio (TTM) NaN
Yield 5.16%
YTD Daily Total Return -23.94%
Beta (5Y Monthly) 1.04
Expense Ratio (net) 0.07%
Inception Date 2012-10-18
Previous Close 56.21
Open 56.28
Bid 56.09 x 21500
Ask 56.10 x 1800
Day's Range 56.04 - 56.29
52 Week Range 51.34 - 77.91
Volume 16366541
Avg. Volume 13305853
AGG
1
0
Net Assets 77.93B
NAV 94.78
PE Ratio (TTM) 121.23
Yield 2.16%
YTD Daily Total Return -15.61%
Beta (5Y Monthly) 1.00
Expense Ratio (net) 0.03%
Inception Date 2003-09-22
Previous Close 94.86
Open 94.98
Bid 95.03 x 4000
Ask 95.04 x 1000
Day's Range 94.88 - 95.07
52 Week Range 93.20 - 115.50
Volume 3230840
Avg. Volume 7105759
IJR
1
0
Net Assets 59.05B
NAV 98.30
PE Ratio (TTM) 2.52
Yield 1.91%
YTD Daily Total Return -13.75%
Beta (5Y Monthly) 1.10
Expense Ratio (net) 0.06%
Inception Date 2000-05-22
Previous Close 98.31
Open 97.71
Bid 96.75 x 800
Ask 96.76 x 800
Day's Range 96.46 - 97.71
52 Week Range 86.40 - 121.45
Volume 1627794
Avg. Volume 3966312
SPX.MI
1 1
0
(Net Assets, Previous Close) NaN 40.12
(NAV, Open) 39.08 40.07
(PE Ratio (TTM), Bid) NaN 39.76 x 0
(Yield, Ask) 1.47% 39.82 x 0
(YTD Daily Total Return, Day's Range) -7.96% 39.78 - 40.07
(Beta (5Y Monthly), 52 Week Range) 0.97 35.92 - 43.65
(Expense Ratio (net), Volume) 0.09% 44691
(Inception Date, Avg. Volume) 2010-03-26 10818
WLDC.MI
1 1
0
(Net Assets, Previous Close) NaN 10.82
(NAV, Open) 10.76 10.79
(PE Ratio (TTM), Bid) NaN 10.72 x N/A
(Yield, Ask) NaN 10.75 x N/A
(YTD Daily Total Return, Day's Range) -9.90% 10.72 - 10.79
(Beta (5Y Monthly), 52 Week Range) 0.00 9.86 - 11.91
(Expense Ratio (net), Volume) 0.30% 1388
(Inception Date, Avg. Volume) 2021-06-02 3893
USRI.PA
1 1
0
(Net Assets, Previous Close) NaN 88.87
(NAV, Open) 88.58 88.82
(PE Ratio (TTM), Bid) NaN 0.00 x 0
(Yield, Ask) 0.00% 0.00 x 0
(YTD Daily Total Return, Day's Range) -8.47% 88.03 - 88.82
(Beta (5Y Monthly), 52 Week Range) 0.97 78.38 - 98.40
(Expense Ratio (net), Volume) 0.08% 8686
(Inception Date, Avg. Volume) 2018-09-11 12653
MEUD.PA
1 1
0
(Net Assets, Previous Close) 34.44M 182.41
(NAV, Open) 180.43 183.27
(PE Ratio (TTM), Bid) NaN 0.00 x 0
(Yield, Ask) 0.00% 0.00 x 0
(YTD Daily Total Return, Day's Range) -13.70% 181.89 - 183.29
(Beta (5Y Monthly), 52 Week Range) 1.02 166.94 - 213.07
(Expense Ratio (net), Volume) 0.15% 828
(Inception Date, Avg. Volume) 2013-04-03 13427
URTH
1
0
Net Assets 1.88B
NAV 107.30
PE Ratio (TTM) NaN
Yield 2.04%
YTD Daily Total Return -19.96%
Beta (5Y Monthly) 1.03
Expense Ratio (net) 0.24%
Inception Date 2012-01-10
Previous Close 107.44
Open 107.39
Bid 106.87 x 900
Ask 106.91 x 900
Day's Range 106.65 - 107.15
52 Week Range 97.44 - 136.75
Volume 110790
Avg. Volume 314506
AEEM.PA
1
0
Net Assets NaN
NAV 4.09
PE Ratio (TTM) NaN
Yield 0.00%
YTD Daily Total Return -19.64%
Beta (5Y Monthly) 1.00
Expense Ratio (net) 0.10%
Inception Date 2018-04-18
Previous Close 4.1855
Open 4.2118
Bid 0.0000 x 0
Ask 0.0000 x 0
Day's Range 4.1650 - 4.2118
52 Week Range 4.0259 - 5.2850
Volume 121967
Avg. Volume 223479
IUHE.AS
1
0
Net Assets NaN
NAV 6.53
PE Ratio (TTM) NaN
Yield 1.36%
YTD Daily Total Return -7.36%
Beta (5Y Monthly) 0.00
Expense Ratio (net) 0.15%
Inception Date 2020-06-15
Previous Close 6.51
Open 6.54
Bid N/A x N/A
Ask N/A x N/A
Day's Range 6.51 - 6.54
52 Week Range 5.94 - 7.20
Volume 448
Avg. Volume 1235
ICOM.L
1
0
Net Assets 1.56B
NAV 7.15
PE Ratio (TTM) NaN
Yield 0.00%
YTD Daily Total Return 13.27%
Beta (5Y Monthly) 0.00
Expense Ratio (net) 0.19%
Inception Date 2017-07-18
Previous Close 7.13
Open 7.19
Bid 7.20 x 0
Ask 7.21 x 0
Day's Range 7.13 - 7.23
52 Week Range 5.90 - 8.65
Volume 407562
Avg. Volume 570475

Conclusion Step 2:

Plan for Step 3 - Portfolio Optimization:¶

Two interesting perspectives: how does the optimal weighting for my own assets looks like & how does the optimal weighting for all assets looks like.

  1. Equal Weighted Holdings
  2. Mean-variance method
In [12]:
# compute the daily returns for each ETF
# since I could not figure out how to clean the data for some ETFs appropiately, I excluded them until I found a solution
all_data = all_data.drop(['WLDC.MI', 'USRI.PA', 'IUHE.AS', 'ICOM.L'], axis=1)
all_data_returns = all_data.pct_change().dropna()
In [13]:
# compute equal weighting
N = len(all_data_returns.columns)
equal_weights = N * [1/N]

# compute the cummulative returns
equal_returns = np.dot(equal_weights,all_data_returns.transpose())
cum_equal_returns =   (1 + equal_returns).cumprod() - 1
cum_equal_returns_perc = pd.Series(100 * cum_equal_returns)
cum_equal_returns_perc.index = all_data_returns.index

# plot them
sns.set_style('darkgrid')
sns.set_palette('colorblind')

plt.figure(figsize=(10,4), dpi=150)
plt.plot(cum_equal_returns_perc)
plt.xlabel('Time')
plt.ylabel('Cummulative Return')
plt.title('Cummulative Return of all ETFs provided by iShares, Amundi & my own with equal weighting.')

plt.tight_layout()
plt.show();

Erläuterung Efficient Frontier: The EF shows the set of optimal portfolios that offer the highest expected return for a given risk level or the lowest risk for a given level of expected return.

In [14]:
# mean-variance method with all data
mu = mean_historical_return(all_data)
S = CovarianceShrinkage(all_data).ledoit_wolf()

ef = EfficientFrontier(mu, S)
weights = ef.max_sharpe()
cleaned_weights = ef.clean_weights()
print(dict(cleaned_weights))
{'IVV': 0.07373, 'IEFA': 0.0, 'AGG': 0.0, 'IJR': 0.0, 'SPX.MI': 0.92627, 'MEUD.PA': 0.0, 'URTH': 0.0, 'AEEM.PA': 0.0}
In [15]:
ef.portfolio_performance(verbose=True)
Expected annual return: 11.8%
Annual volatility: 18.4%
Sharpe Ratio: 0.53
Out[15]:
(0.11812858236785169, 0.18379300475950164, 0.533908145722171)
In [16]:
ef_plot = EfficientFrontier(mu, S)
plotting.plot_efficient_frontier(ef_plot)
Out[16]:
<AxesSubplot:xlabel='Volatility', ylabel='Return'>
In [17]:
# plot development with the efficient frontier weighting
ef_weights = [0.01692, 0.0, 0.0, 0.0, 0.98308, 0.0, 0.0,  0.0]

ef_returns = np.dot(ef_weights, all_data_returns.transpose())
cum_ef_returns =   (1 + ef_returns).cumprod() - 1
cum_ef_returns_perc = pd.Series(100 * cum_ef_returns)
cum_ef_returns_perc.index = all_data_returns.index

# plot them
sns.set_style('darkgrid')
sns.set_palette('colorblind')

plt.figure(figsize=(10,4), dpi=150)
plt.plot(cum_ef_returns_perc)
plt.xlabel('Time')
plt.ylabel('Cummulative Return')
plt.title('Cummulative Return of all ETFs provided by iShares, Amundi & my own with efficient frontier weighting.')

plt.tight_layout()
plt.show();
In [18]:
# constraints: the weighting of asset 1 must be greater or equal to 20%, weighting of asset 3 must be equal to 15%
# and the weighting of asset 4 and 5 must be added to be smaller or equal than 10%
ef_plot_c1 = EfficientFrontier(mu, S)
ef_plot_c1.add_constraint(lambda w: w[0] >= 0.2)
ef_plot_c1.add_constraint(lambda w: w[2] == 0.15)
ef_plot_c1.add_constraint(lambda w: w[3] + w[4] <= 0.10)

plotting.plot_efficient_frontier(ef_plot_c1)
Out[18]:
<AxesSubplot:xlabel='Volatility', ylabel='Return'>
In [19]:
# mean-variance method my own
mu2 = mean_historical_return(antons_data)
S2 = CovarianceShrinkage(antons_data).ledoit_wolf()

ef2 = EfficientFrontier(mu2, S2)
weights2 = ef2.max_sharpe()
cleaned_weights2 = ef2.clean_weights()
print(dict(cleaned_weights2))

antons_data_returns = antons_data.pct_change().dropna()
ef_weights2 = [0.0, 0.0, 0.83616, 0.16384]

ef2_returns = np.dot(ef_weights2, antons_data_returns.transpose())
cum_ef2_returns =   (1 + ef2_returns).cumprod() - 1
cum_ef2_returns_perc = pd.Series(100 * cum_ef2_returns)
cum_ef2_returns_perc.index = antons_data_returns.index
{'URTH': 0.0, 'AEEM.PA': 0.0, 'IUHE.AS': 0.82098, 'ICOM.L': 0.17902}
In [20]:
sns.set_style('darkgrid')
sns.set_palette('colorblind')

fig, ax = plt.subplots(figsize=(10,10), dpi=200)

ax.plot(cum_equal_returns_perc, label="equal", lw=2)
ax.plot(cum_ef_returns_perc, label="ef_all", lw=2)
ax.plot(cum_ef2_returns_perc, label="ef_own",lw=2)

ax.set_xlabel('Time')
ax.set_ylabel('Relative change')

fig.suptitle('Relative change for all 3 categories of ETFs using equal and efficient frontier weighting', fontsize=20)

ax.xaxis.set_major_locator(dates.YearLocator())
ax.xaxis.set_major_formatter(dates.DateFormatter("%Y"))
ax.tick_params(axis="x", which="major",rotation=0, pad=5)

ax.legend(loc ='upper left')

plt.tight_layout()
plt.show();

Conslusion Portfolio Optimization: it is clearly shown, that my past decisions lead to missing opportunities or missing growth, resp. Therefore, my choices were rather bad.

Iternary for Step 4¶

Design a interactive dashboard including the findings from step 1 to 3. This step will be designed in another python file.